#region Using Statements
using System.Collections.Generic;
using System.Diagnostics;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

//using Xfinity.Entities;
//using Xfinity.Engine.Plugins;
using System;
using Xfinity.Entities;
using Xfinity.Cameras;
#endregion
using System.Reflection;
namespace Xfinity.Engine
{
    /// <summary>
    /// A helper class for misc. graphics-related functions and constants.
    /// </summary>
    public static class GraphicsHelper
    {
        /// <summary>
        /// Used for rounding errors.
        /// </summary>
        public const double Epsilon = 0.000001;

        private static Camera cam;
        /// <summary>
        /// The active camera.
        /// </summary>
        public static Camera ActiveCamera
        {
            [DebuggerStepThrough]
            get
            {
                System.Diagnostics.Debug.Assert(cam != null);
                return cam;
            }
            [DebuggerStepThrough]
            set
            {
                System.Diagnostics.Debug.Assert(value != null);
                cam = value;
            }
        }

        private static EffectPool effectPool = new EffectPool();

        /// <summary>
        /// 
        /// </summary>
        public static EffectPool EffectPool
        {
            [DebuggerStepThrough]
            get { return effectPool; }
            [DebuggerStepThrough]
            set { effectPool = value; }
        }



        private static SpriteFont defaultFont;

        /// <summary>
        /// The default font.
        /// </summary>
        public static SpriteFont DefaultFont
        {
            [DebuggerStepThrough]
            get { return defaultFont; }
            [DebuggerStepThrough]
            set { defaultFont = value; }
        }
        #region BloomEnabled
        private static bool bloomEnabled = true;

        /// <summary>
        /// BloomEnabled.
        /// </summary>
        public static bool BloomEnabled
        {
            get { return bloomEnabled; }
            set { bloomEnabled = value; }
        }

        #endregion
        [DebuggerStepThrough]
        public static void DrawText(string text, Vector2 topLeft, Color color)
        {
            spriteBatch.Begin();
            spriteBatch.DrawString(defaultFont, text, topLeft, color);
            spriteBatch.End();
            GraphicsDevice.RenderState.DepthBufferEnable = true;
        }

        private static bool drawModels = true;

        /// <summary>
        /// Gets or sets wether to draw models.
        /// </summary>
        public static bool DrawModels
        {
            [DebuggerStepThrough]
            get { return drawModels; }
            [DebuggerStepThrough]
            set { drawModels = value; }
        }


        private static SpriteBatch spriteBatch;

        /// <summary>
        /// The global sprite batch.
        /// </summary>
        public static SpriteBatch SpriteBatch
        {
            [DebuggerStepThrough]
            get { return spriteBatch; }
            [DebuggerStepThrough]
            set { spriteBatch = value; }
        }


        private static GraphicsDevice gd;
        /// <summary>
        /// The game's GraphicsDevice.
        /// </summary>
        public static GraphicsDevice GraphicsDevice
        {
            [DebuggerStepThrough]
            get { return gd; }
            [DebuggerStepThrough]
            set { gd = value; }
        }
        /// <summary>
        /// A more accurate version of MathHelper.ToRadians().
        /// </summary>
        /// <param name="degrees">The number to convert to radians.</param>
        /// <returns>The value in radians.</returns>
        [DebuggerStepThrough]
        public static double ToRadians(double degrees)
        {
            return degrees * 0.017453292519943295769236907684886;
        }
        /// <summary>
        /// A more accurate version of MathHelper.ToDegrees().
        /// </summary>
        /// <param name="radians">The number to convert to degrees.</param>
        /// <returns>The value in degrees.</returns>
        [DebuggerStepThrough]
        public static double ToDegrees(double radians)
        {
            return radians * 57.295779513082320876798154814105;
        }
        /// <summary>
        /// Draws a collection of Models, without testing wether it's in view.
        /// </summary>
        /// <param name="modelList">The collection of models.</param>
        [DebuggerStepThrough]
        public static void DrawModelList(IEnumerable<Model> modelList)
        {
            if (!drawModels)
            {
                return;
            }
            System.Diagnostics.Debug.Assert(modelList != null);
            foreach (Model m in modelList)
            {
                DrawModel(m);
            }
        }
        /// <summary>
        /// Draws a Model, without testing wether in it's view.
        /// </summary>
        /// <param name="m">The model to draw.</param>
        [DebuggerStepThrough]
        public static void DrawModel(Model m)
        {
            if (!drawModels)
            {
                return;
            }
            System.Diagnostics.Debug.Assert(m != null);
            vm = cam.View;

            proj = cam.Projection;
            for (int meshNumber = 0; meshNumber < m.Meshes.Count; meshNumber++)
            {
                mesh = m.Meshes[meshNumber];
                world = mesh.ParentBone.Transform;

                for (int i = 0; i < mesh.Effects.Count; i++)
                {
                    if (mesh.Effects[i] is BasicEffect)
                    {
                        be = mesh.Effects[i] as BasicEffect;
                        be.PreferPerPixelLighting = true;
                        be.EnableDefaultLighting();
                        be.View = vm;
                        be.Projection = proj;
                        be.World = world;
                    }
                    else
                    {
                        e = mesh.Effects[i];
                        EffectParameter param;

                        param = e.Parameters.GetParameterBySemantic("View");
                        if (param != null)
                        {
                            param.SetValue(vm);
                        }

                        param = e.Parameters.GetParameterBySemantic("Projection");
                        if (param != null)
                        {
                            param.SetValue(proj);
                        }

                        param = e.Parameters.GetParameterBySemantic("World");
                        if (param != null)
                        {
                            param.SetValue(world);
                        }
                    }
                }

                mesh.Draw();
            }
        }
        /// <summary>
        /// Draws a collection of Models.
        /// </summary>
        /// <param name="modelList">The collection to draw.</param>
        [DebuggerStepThrough]
        public static void DrawClippedModelList(IEnumerable<Model> modelList)
        {
            if (!drawModels)
            {
                return;
            }
            System.Diagnostics.Debug.Assert(modelList != null);
            foreach (Model m in modelList)
            {
                DrawClippedModel(ref identityMatrix, m);
            }
        }
        private static Matrix identityMatrix = Matrix.Identity;
        /// <summary>
        /// Draws a collection of Models.
        /// </summary>
        /// <param name="modelList">The collection to draw.</param>
        [DebuggerStepThrough]
        public static void DrawClippedModel(IEnumerable<IPhysicalEntity> modelList)
        {
            if (!drawModels)
            {
                return;
            }
            System.Diagnostics.Debug.Assert(modelList != null);
            foreach (IPhysicalEntity m in modelList)
            {
                m.Draw();
            }
        }
        private static Matrix vm, proj, world;
        private static Effect e;
        private static BasicEffect be;
        private static ModelMesh mesh;
        private static Matrix[] transforms = new Matrix[1];
        /// <summary>
        /// Draws a Model.
        /// </summary>
        /// <param name="m">The model to draw.</param>
        //[DebuggerStepThrough]
        public static void DrawClippedModel(ref Matrix worldMatrix, Model m)
        {
            if (!drawModels)
            {
                return;
            }
            if (m == null)
            {
                //return;
                throw new ArgumentNullException("m");
            }
            //Array.Resize<Matrix>(ref transforms, m.Bones.Count);
            transforms = new Matrix[m.Bones.Count];
            m.CopyAbsoluteBoneTransformsTo(transforms);

            System.Diagnostics.Debug.Assert(m != null);
            System.Diagnostics.Debug.Assert(cam != null);
            
            vm = cam.View;
            proj = cam.Projection;
            world = cam.World * worldMatrix;
            for (int meshNumber = 0; meshNumber < m.Meshes.Count; meshNumber++)
            {
                mesh = m.Meshes[meshNumber];
                Matrix transWorld = world * transforms[mesh.ParentBone.Index];

                bool t;
                //foreach (BasicEffect e in mesh.Effects)
                //{
                //    Debug.Fail(e.Texture.Name);
                //}
                BoundingSphere s = new BoundingSphere(transWorld.Translation + mesh.BoundingSphere.Center, mesh.BoundingSphere.Radius + transforms[meshNumber].Determinant());
                t = cam.Frustum.Intersects(s);
                if (t)
                {
                    for (int i = 0; i < mesh.Effects.Count; i++)
                    {
                        if (mesh.Effects[i] is BasicEffect)
                        {
                            be = mesh.Effects[i] as BasicEffect;
                            be.PreferPerPixelLighting = true;
                            be.View = vm;
                            be.Projection = proj;
                            be.World = transWorld;

                        }
                        else
                        {
                            e = mesh.Effects[i];
                            EffectParameter param;

                            param = e.Parameters.GetParameterBySemantic("VIEW");
                            if (param != null)
                            {
                                param.SetValue(vm);
                            }

                            param = e.Parameters.GetParameterBySemantic("PROJECTION");
                            if (param != null)
                            {
                                param.SetValue(proj);
                            }

                            param = e.Parameters.GetParameterBySemantic("WORLD");
                            if (param != null)
                            {
                                param.SetValue(transforms[mesh.ParentBone.Index]);
                            }
                        }
                    }
                    mesh.Draw();
                }
            }
        }
    }
}